<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=507902
-->
<head>
  <title>Test for Bug 507902</title>
  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=507902">Mozilla Bug 507902</a>

<iframe id="testFrameElem"></iframe>

<pre id="test">
<script class="testbody" type="text/javascript">

//
// Mochitest to test nsImageFrame icons
//
// The 'loading' icon should be displayed up until we have enough image
// data to determine the frame size.
//
// The 'broken' icon should be displayed when the URL is invalid (either
// a bad server or a file that fails to be sniffed to an appropriate
// mimetype).
//

// Boilerplate
gWindowUtils = SpecialPowers.getDOMWindowUtils(window);

// URL + paths
//
// We have a separate copy of the icons in the test directory to
// avoid any firefox caching mechanisms that might affect the
// behavior of the load.
var us = window.location.href;
var baseURL = us.substring(0, us.lastIndexOf('/') + 1);
var loadIconFilename = "file_LoadingImageReference.png";
var imageFilename = "file_Dolske.png";
var brokenIconFilename = "file_BrokenImageReference.png";
var serverFilename = "file_IconTestServer.sjs";
var serverContinueFlag = "?continue=true";
var bogusFilename = "oneuponatimewhendolskewasyoung.png";

// Our test image element, inside a div, inside an iframe
var testFrameElem = document.getElementById("testFrameElem");
var innerDoc = testFrameElem.contentWindow.document;
var divContainer = innerDoc.createElement("div");
divContainer.style.cssFloat = "left";
innerDoc.body.appendChild(divContainer);
var testImageElem = new Image();
divContainer.appendChild(testImageElem);
var pingImage = new Image();

// Set up the canvases
var canvases = {};
var canvasNames = [ "brokenIconTest",  "brokenIconReference",
                    "loadingIconTest", "loadingIconReference",
                    "loadedTest",      "loadedReference" ];
var windowElem = document.documentElement;
for (var i in canvasNames) {
  var can = document.createElement("canvas");
  can.setAttribute("width", windowElem.getAttribute("width"));
  can.setAttribute("height", windowElem.getAttribute("height"));
  canvases[canvasNames[i]] = can;

  // When the image frame has no idea how to size itself, it sizes itself
  // to dimensions capable of displaying the alt feedback icons. However, if
  // the image has been loaded before, something (I don't know what) seems to
  // remember the size of the last successful image for that URL. So when we
  // create a new image frame for that URL, it uses that size until it hears
  // something different. This happens through a refresh (not sure if this is
  // desired behavior). This means that if you refresh the test, the "loading"
  // icon for the test image will appear with a border that stretches further
  // right and down, because that URL previously displayed an image with larger
  // dimensions. This causes the verify stage to fail. To allow for
  // successful test refreshes (only useful for people, not automated tests),
  // we add a clipping region so that we see the left and top borders, along
  // with the image, but not the bottom and right borders.

  if ((i > 1) && (i < 4)) {
    var ctx = can.getContext("2d");
    ctx.beginPath();
    ctx.rect(0,0, 30, 30);
    ctx.clip();
   }

}

// Stage 1 - Load the reference image for the broken icon
function loadBrokenIconReference() {

  // Debugging - Let's see if setting onload after src is a problem
  testImageElem.onload = function(event) { dump("test_bug507902.html DEBUG - uh oh, placeholder onload 1 called\n");};

  // Debug - Figure out if we're getting an onerror instead of onload
  testImageElem.onerror = function(event) {dump("test_bug507902.html DEBUG - Got onerror for testImageElem!\n");};

  testImageElem.src = baseURL + brokenIconFilename;
  stageTransition();
}

// Stage 2 - Draw the reference image for the broken icon to a canvas
function drawBrokenIconReference() {

  enableBorderAndPad();
  drawWindowToCanvas("brokenIconReference");
  disableBorderAndPad();

  stageTransition();
}

// Stage 3 - Load the reference image for the loading icon
function loadLoadingIconReference() {

  // Debugging - Let's see if setting onload after src is a problem
  testImageElem.onload = function(event) { dump("test_bug507902.html DEBUG - uh oh, placeholder onload 3 called\n");};

  testImageElem.src = baseURL + loadIconFilename;
  stageTransition();
}

// Stage 4 - Draw the reference image for the loading icon to a canvas
function drawLoadingIconReference() {

  enableBorderAndPad();
  drawWindowToCanvas("loadingIconReference");
  disableBorderAndPad();

  stageTransition();
}

// Stage 5 - Try to load a broken image
function loadBrokenImage() {
  resetImage();
  testImageElem.src = baseURL + bogusFilename;
  stageTransition();
}

// Stage 6 - Draw the screen to a canvas. This should hopefully
// be the broken icon.
function drawBrokenIcon() {
  drawWindowToCanvas("brokenIconTest");
  stageTransition();
}

// Stage 7 - Load the reference image for the test image
function loadImageReference() {
  resetImage();

  // Debugging - Let's see if setting onload after src is a problem
  testImageElem.onload = function(event) { dump("test_bug507902.html DEBUG - uh oh, placeholder onload 7 called\n");};

  testImageElem.src = baseURL + imageFilename;
  stageTransition();
}

// Stage 8 - Draw the reference image for the test image to a canvas
function drawImageReference() {
  drawWindowToCanvas("loadedReference");
  stageTransition();
}

// Stage 9 - Start a load of the test image from the delay-generating server
function startServerLoad() {

  // Reset the image
  resetImage();

  // Debugging info so we can figure out the hang
  dump("test_bug507902.html DEBUG - starting server load\n");

  // Load the image
  testImageElem.src = baseURL + serverFilename;
  stageTransition();
}

// Stage 10 - Draw the screen to a canvas. This should hopefully be the loading
// icon.
function drawLoadingIcon() {

  // Debugging info so we can figure out the hang
  dump("test_bug507902.html DEBUG - drawing loading icon\n");

  drawWindowToCanvas("loadingIconTest");
  stageTransition();
}

// Stage 11 - Tell the server to continue.
function signalServerContinue() {

  // Debugging info so we can figure out the hang
  dump("test_bug507902.html DEBUG - signaling server to continue\n");

  pingImage.src = baseURL + serverFilename + serverContinueFlag;
  stageTransition();
}

// Stage 12 - Draw the screen to a canvas. This should hopefully be the loaded
// test image.
function drawLoadedImage() {
  drawWindowToCanvas("loadedTest");
  stageTransition();
}


// Stage 13 - Verify That the appropriate canvases match
function verifyCanvases() {

  // Verify the broken icon
  ok(canvasesAreEqual("brokenIconTest", "brokenIconReference"),
     "Window drawn on broken load should match broken icon reference");

  // Verify the loading icon
  ok(canvasesAreEqual("loadingIconTest", "loadingIconReference"),
     "Window drawn mid-load should match loading icon reference");

  // Verify the loaded image
  ok(canvasesAreEqual("loadedTest", "loadedReference"),
     "Window drawn post-load should match reference image");

  stageTransition();
}

// We have a bunch of different things that need to happen in order
// with different transitions. We make a "stage table" here where
// each entry contains the stage function ('fn') and a transition
// ('trans'), which can be one of the following:
// "instant" - Just calls the next stage directly
// "onload" - Sets the next stage as an onload event for the image element
// "onerror" - Sets the next stage as an onerror event for the image element
// integer  - Sets the next stage to be called after the given timeout duration
// "finish" - Finish the test
var testStages = [
  { "fn" : loadBrokenIconReference,  "trans" : "onload"},
  { "fn" : drawBrokenIconReference,  "trans" : "instant"},
  { "fn" : loadLoadingIconReference, "trans" : "onload" },
  { "fn" : drawLoadingIconReference, "trans" : "instant" },
  { "fn" : loadBrokenImage,          "trans" : "onerror" },
  { "fn" : drawBrokenIcon,           "trans" : "instant" },
  { "fn" : loadImageReference,       "trans" : "onload" },
  { "fn" : drawImageReference,       "trans" : "instant" },
  // XXXbholley - We use a timeout here because resetting the
  // image doesn't seem to be quite synchronous. If we make
  // this transition "instant", then the drawImage call draws
  // an empty (0,0,0,0) rect to the canvas and we're left with
  // whatever was there before. I don't know of any good event
  // mechanism to figure out when the image frame is bootstrapped
  // enough to display the loading image, so I did trial-and-error
  // with timeouts. 50ms seems to be enough time for things to work
  // reliably, so *= 6 for good measure.
  { "fn" : startServerLoad,          "trans" : 300 },
  { "fn" : drawLoadingIcon,          "trans" : "instant" },
  { "fn" : signalServerContinue,     "trans" : "onload" },
  { "fn" : drawLoadedImage,          "trans" : "instant" },
  { "fn" : verifyCanvases,           "trans" : "finish" } ];
var currentStage = 0;

// Transition function called at the end of each stage
function stageTransition() {

  // Debugging info so we can figure out the hang
  dump("test_bug507902.html DEBUG - Current Stage: " + currentStage + "\n");

  // What's our transition?
  var trans = testStages[currentStage++].trans;

  // If the transition is finish, stop now before we access out of bounds
  if (trans == "finish") {
    makeCanvasesVisible(); // Useful for debugging
    SimpleTest.finish();
    return;
  }

  // Otherwise, get the next function
  var nextfn = testStages[currentStage].fn;

  // Switch based on transition
  switch (trans) {

    // Continue right away
    case "instant":
      nextfn();
      break;

    // Continue after we get an onload event on the test image
    case "onload":
      testImageElem.onload = function(event) {testImageElem.onload = undefined; nextfn();};
      break;

    // Continue after we get an onerror event on the test image
    case "onerror":
      testImageElem.onerror = function(event) {testImageElem.onerror = undefined; nextfn();};
      break;

    // Timeout
    default:
      setTimeout(nextfn, trans);
      break
  }
}

// Lots if asynchronous behavior here
SimpleTest.waitForExplicitFinish();

// Catch somebody's eye
dump("This test is failing intermittently, see bug 510001 - If you see orange here, please paste the following debugging output on the bug!\n");

// Kick off the test by invoking the first stage. The stages call each other
testStages[0].fn();


// We need to get rid of the old image element and make a new one. If we
// don't, the "current/pending" machinery will display the old image until
// the new one is loaded, so we won't see the loading icon.
function resetImage() {
  divContainer.removeChild(testImageElem);
  testImageElem = null;
  testImageElem = new Image();
  divContainer.appendChild(testImageElem);
}

//
// Makes the canvases visible. Called before the tests finish. This is useful for
// debugging.
//
function makeCanvasesVisible() {
  for (var i = 0; i < canvasNames.length - 1; i += 2) {
    var title = document.createElement("h3");
    title.innerHTML = canvasNames[i] + ", " + canvasNames[i+1] + ":";
    document.body.appendChild(title);
    var myDiv = document.createElement("div");
    myDiv.appendChild(canvases[canvasNames[i]]);
    myDiv.appendChild(canvases[canvasNames[i+1]]);
    document.body.appendChild(myDiv);
  }
}

//
// Enables and disables bordering/padding to mimic the look of alt feedback icons
//
function enableBorderAndPad() {
  divContainer.style.border = "1px";
  divContainer.style.borderStyle = "inset";
  testImageElem.style.padding = "3px";
}

function disableBorderAndPad() {
  testImageElem.style.padding = 0;
  divContainer.style.border = "0px";
  divContainer.style.borderStyle = "";
}

//
// Helper canvas methods. This is mostly copped directly from the reftest framework
//

function drawWindowToCanvas(canvasName) {
  var win = testFrameElem.contentWindow;
  var ctx = canvases[canvasName].getContext("2d");
  // drawWindow always draws one canvas pixel for each CSS pixel in the source
  // window, so scale the drawing to show the zoom (making each canvas pixel be one
  // device pixel instead)
  ctx.drawWindow(win, win.scrollX, win.scrollY,
                 Math.ceil(canvases[canvasName].width),
                 Math.ceil(canvases[canvasName].height),
                 "rgb(255,255,255)");
}

function canvasesAreEqual(canvas1Name, canvas2Name) {
  var c1 = canvases[canvas1Name];
  var c2 = canvases[canvas2Name];
  var differences = gWindowUtils.compareCanvases(c1, c2, {});
  return (differences == 0);
}

</script>
</pre>
</body>
</html>
